/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package main.pso;

import main.*;

import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Scanner;
import java.util.StringTokenizer;

import chartcreator.ChartGenerator;

/**
 * 
 * @author dalexandre
 */
public abstract class PSO {
	public static final Random random = new Random(System.nanoTime());

	protected double[] euclidianDistances;
	protected Particle globalBestPosition;
	protected List<DataVector> trainningSet;
	protected List<Particle> swarm;
	protected String fileName;
	protected double errorTotal;

	public double getErrorTotal() {
		double acertos = 0;
		double erroTotal = 0;
		Integer[] numberInClasses = new Integer[this.globalBestPosition
				.getCentroids().length];
		ArrayList<Integer> alreadyCountedList = new ArrayList<Integer>();
		for (int i = 0; i < this.globalBestPosition.getCentroids().length; i++) {
			// zera tudo na lista que conta a quantidade de objetos em cada
			// classe
			for (int j = 0; j < numberInClasses.length; j++) {
				numberInClasses[j] = 0;
			}
			// vai contando quantos objetos ha em cada classe.
			for (int j = 0; j < this.trainningSet.size(); j++) {
				if (this.trainningSet.get(j).getCentroid().getId() == i) {
					int aux = numberInClasses[this.trainningSet.get(j)
							.getRealClass() - 1];
					aux++;
					numberInClasses[this.trainningSet.get(j).getRealClass() - 1] = aux;
				}
			}
			/*
			 * se foi uma classe que já foi contara, deverá haver ela não
			 * pode mais ser contada por isso é colocado um número negativo no
			 * valor dessa classe, caso a classificação seja muito dificil.
			 */
			if (!alreadyCountedList.isEmpty()) {
				for (int j = 0; j < numberInClasses.length; j++) {
					if (alreadyCountedList.contains(j)) {
						numberInClasses[j] = -numberInClasses[j];
					}
				}
			}
			// pega o valor que tem a maioria e o indice dele.
			int max = 0;
			int indexMax = 0;
			for (int j = 0; j < numberInClasses.length; j++) {
				if (numberInClasses[j] > max) {
					indexMax = j;
					max = numberInClasses[j];
				}
			}
			alreadyCountedList.add(indexMax);
			int erros = 0;
			/*
			 * aqui se soma os erros ao erro total
			 */
			for (int j = 0; j < numberInClasses.length; j++) {
				if (numberInClasses[j] < max) {
					erros += Math.abs(numberInClasses[j]);
				}
			}
			erroTotal += erros;
		}

		errorTotal = erroTotal * 100
				/ new Integer(this.trainningSet.size()).doubleValue();

		
		return errorTotal;
	}

	public void setErrorTotal(double errorTotal) {
		this.errorTotal = errorTotal;
	}

	protected double wMax = 0.171, wMin = 0.01, c1 = .2, c2 = .2;

	/**
	 * Constr�i a classe PSO - Particle Swarm Optimization
	 * 
	 * @param fileName
	 *            caminho do artivo no qual se encontra a base de dados.
	 */
	public PSO(String fileName) {
		this.fileName = fileName;
		this.trainningSet = new ArrayList<DataVector>();
		this.swarm = new ArrayList<Particle>();
	}

	public abstract void execute(int k, int numberOfParticles,
			int numberOfIterations);

	public abstract void initializeParticles(int k, int numberOfParticles);

	public String getError() {
		String result = "Matrix de resultado:\n\n";
		double acertos = 0;
		double erroTotal = 0;
		Integer[] numberInClasses = new Integer[this.globalBestPosition
				.getCentroids().length];
		ArrayList<Integer> alreadyCountedList = new ArrayList<Integer>();
		for (int i = 0; i < this.globalBestPosition.getCentroids().length; i++) {
			// zera tudo na lista que conta a quantidade de objetos em cada
			// classe
			for (int j = 0; j < numberInClasses.length; j++) {
				numberInClasses[j] = 0;
			}
			// vai contando quantos objetos ha em cada classe.
			for (int j = 0; j < this.trainningSet.size(); j++) {
				if (this.trainningSet.get(j).getCentroid().getId() == i) {
					int aux = numberInClasses[this.trainningSet.get(j)
							.getRealClass() - 1];
					aux++;
					numberInClasses[this.trainningSet.get(j).getRealClass() - 1] = aux;
				}
			}
			for (int j = 0; j < numberInClasses.length; j++) {
				result += " " + numberInClasses[j] + "  ";
			}
			result += "\n-------------------------------\n";
			/*
			 * se foi uma classe que já foi contara, deverá haver ela não
			 * pode mais ser contada por isso é colocado um número negativo no
			 * valor dessa classe, caso a classificação seja muito dificil.
			 */
			if (!alreadyCountedList.isEmpty()) {
				for (int j = 0; j < numberInClasses.length; j++) {
					if (alreadyCountedList.contains(j)) {
						numberInClasses[j] = -numberInClasses[j];
					}
				}
			}
			// pega o valor que tem a maioria e o indice dele.
			int max = 0;
			int indexMax = 0;
			for (int j = 0; j < numberInClasses.length; j++) {
				if (numberInClasses[j] > max) {
					indexMax = j;
					max = numberInClasses[j];
				}
			}
			alreadyCountedList.add(indexMax);
			int erros = 0;
			/*
			 * aqui se soma os erros ao erro total
			 */
			for (int j = 0; j < numberInClasses.length; j++) {
				if (numberInClasses[j] < max) {
					erros += Math.abs(numberInClasses[j]);
				}
			}
			erroTotal += erros;
		}

		errorTotal = erroTotal * 100
				/ new Integer(this.trainningSet.size()).doubleValue();

		result += "\n\nErro Total : " + errorTotal+ " Erro quadratico \n" + this.globalBestPosition.getFitness();
		return result;
	}
	protected void mutate(Particle particle) {
    	for (int i = 0; i < particle.getCentroids().length; i++) {

            for (int j = 0; j < particle.getCentroids()[i].getPosition().length; j++) {

            	particle.getCentroids()[i].getPosition()[j] += (random.nextDouble()/10);
                   
            }

        }
    }
	protected ChartGenerator chart = new ChartGenerator("PSO");

	public void showChartClassification() {
		ArrayList<ArrayList<DataVector>> dadosGrafico = new ArrayList<ArrayList<DataVector>>();

		for (int i = 0; i < this.globalBestPosition.getCentroids().length; i++) {
			dadosGrafico.add((ArrayList<DataVector>) this.globalBestPosition
					.getCentroids()[i].getAssignedDataVectors());
		}
		chart.setData(dadosGrafico, 1, 2);

	}

	public void showChartParticlesMove() {
		ArrayList<ArrayList<DataVector>> dadosGrafico = new ArrayList<ArrayList<DataVector>>();

		for (int i = 0; i < this.swarm.size(); i++) {
			ArrayList<DataVector> listaDados = new ArrayList<DataVector>();
			for (int j = 0; j < globalBestPosition.getCentroids().length; j++) {
				DataVector dado = new DataVector(this.trainningSet.get(0)
						.getPosition().length);
				dado.setPosition(this.swarm.get(i).getCentroids()[j]
						.getPosition());
				listaDados.add(dado);
			}
			dadosGrafico.add(listaDados);
		}
		chart.setData(dadosGrafico, 1, 2);

	}

	public void evaluate(int k, Particle particle) {

		for (DataVector trainningData : trainningSet) {

			for (int j = 0; j < k; j++) {
				this.euclidianDistances[j] = Util.calculateEuclidianDistance(
						trainningData, particle.getCentroids()[j]);
			}

			this.assignTrainningDataToNearestCentroid(trainningData, particle);

		}

	}

	protected void assignTrainningDataToNearestCentroid(
			DataVector trainningData, Particle particle) {

		int indexOfNearestCentroid = 0;

		for (int i = 1; i < this.euclidianDistances.length; i++) {

			if (this.euclidianDistances[i] < this.euclidianDistances[indexOfNearestCentroid]) {
				indexOfNearestCentroid = i;
			}

		}

		if (trainningData.getCentroid() != null) {
			trainningData.getCentroid().getAssignedDataVectors()
					.remove(trainningData);
		}

		trainningData
				.setCentroid(particle.getCentroids()[indexOfNearestCentroid]);

		if (particle.getCentroids()[indexOfNearestCentroid]
				.getAssignedDataVectors().indexOf(trainningData) == -1) {
			particle.getCentroids()[indexOfNearestCentroid]
					.getAssignedDataVectors().add(trainningData);
		}

	}

	protected void initializeTrainingSet() {

		Scanner sc = null;
		StringTokenizer tokens = null;

		int numAttributes = 0;
		DataVector trainningData = null;

		double minValue = Double.MAX_VALUE;
		double maxValue = Double.MIN_VALUE;
		double upperBound = 1;

		try {

			sc = new Scanner(new File(this.fileName));

			if (sc.hasNextLine()) {

				tokens = new StringTokenizer(sc.nextLine());

				numAttributes = tokens.countTokens();

				if (numAttributes <= 1) {
					// System.out.println("Error while reading the file.");
				}

			}

			while (sc.hasNextLine()) {

				tokens = new StringTokenizer(sc.nextLine());

				int numTokens = tokens.countTokens();

				if (numTokens != numAttributes) {
					// System.out.println("Error while reading the file.");
				}

				trainningData = new DataVector(numTokens - 1);

				trainningData.setRealClass(Integer.valueOf(tokens.nextToken()));

				for (int i = 0; i < numTokens - 1; i++) {

					trainningData.getPosition()[i] = Double.valueOf(tokens
							.nextToken());

					if (trainningData.getPosition()[i] > maxValue) {
						maxValue = trainningData.getPosition()[i];
					}

					if (trainningData.getPosition()[i] < minValue) {
						minValue = trainningData.getPosition()[i];
					}

				}

				trainningSet.add(trainningData);

			}

		} catch (IOException ex) {
			System.out.println(ex.getMessage());
		} finally {
			sc.close();
		}

		normalizeTrainningSet(maxValue, minValue, upperBound);
	}

	protected void calculateVelocity(Particle particle, double w) {

		for (int i = 0; i < particle.getCentroids().length; i++) {

			for (int j = 0; j < particle.getCentroids()[i].getPosition().length; j++) {

				particle.getVelocities()[i].getVelocities()[j] = (w * particle
						.getVelocities()[i].getVelocities()[j])
						+ (c1 * Math.random() * (particle
								.getLocalBestPosition().getCentroids()[i]
								.getPosition()[j] - particle.getCentroids()[i]
								.getPosition()[j]))
						+ (c2 * Math.random() * (this.globalBestPosition
								.getCentroids()[i].getPosition()[j] - particle
								.getCentroids()[i].getPosition()[j]));

			}

		}

	}

	protected void updateGlobalBestPosition() {

		int indexOfGlobalBestPosition = -1;

		if (this.globalBestPosition == null) {
			this.globalBestPosition = this.swarm.get(0).clone();
			indexOfGlobalBestPosition = 0;
		}

		for (int i = 0; i < this.swarm.size(); i++) {

			if (this.swarm.get(i).getFitness() < this.globalBestPosition
					.getFitness()) {
				indexOfGlobalBestPosition = i;
			}

		}

		if (indexOfGlobalBestPosition != -1) {
			this.globalBestPosition = this.swarm.get(indexOfGlobalBestPosition)
					.clone();
			// System.out.println("trocou de melhor posi��o global. = "
			// + this.globalBestPosition.getFitness());
			/*
			 * System.out.print(this.globalBestPosition.getCentroids()[0].
			 * getPosition ()[0]+", ");
			 * System.out.print(this.globalBestPosition.getCentroids
			 * ()[0].getPosition()[1]+"\n");
			 * System.out.print(this.globalBestPosition
			 * .getCentroids()[1].getPosition()[0]+", ");
			 * System.out.print(this.globalBestPosition
			 * .getCentroids()[1].getPosition()[1]+"\n");
			 * System.out.print(this.globalBestPosition
			 * .getCentroids()[2].getPosition()[0]+", ");
			 * System.out.print(this.globalBestPosition
			 * .getCentroids()[2].getPosition()[1]+"\n\n");
			 */}

	}

	protected void updateParticlesPositions(double w) {

		for (Particle particle : swarm) {

			this.calculateVelocity(particle, w);
			particle.updatePosition();

		}

	}

	public void printTrainningSet() {

		for (DataVector trainningData : this.trainningSet) {

			for (int i = 0; i < trainningData.getPosition().length; i++) {

				System.out.print(trainningData.getPosition()[i] + " ");

			}

			System.out.print(" -- ");

			if (trainningData.getCentroid() != null) {
				System.out.print(trainningData.getCentroid().getId());
			}

			System.out.println();

		}

	}

	protected void normalizeTrainningSet(double maxValue, double minValue,
			double upperBound) {

		for (DataVector dataVector : trainningSet) {

			for (int i = 0; i < dataVector.getPosition().length; i++) {

				dataVector.getPosition()[i] = ((dataVector.getPosition()[i] - minValue) * upperBound)
						/ (maxValue - minValue);

			}
		}

	}

	protected void recalculateCentroid(Particle kmeansResult) {

		for (Centroid centroid : kmeansResult.getCentroids()) {
			for (int i = 0; i < centroid.getAssignedDataVectors().get(0)
					.getPosition().length; i++) {
				centroid.getPosition()[i] = 0;
			}
		}

		for (Centroid centroid : kmeansResult.getCentroids()) {
			for (DataVector trainingData : centroid.getAssignedDataVectors()) {
				for (int i = 0; i < trainingData.getPosition().length; i++) {
					centroid.getPosition()[i] += trainingData.getPosition()[i];
				}
			}
			for (int i = 0; i < centroid.getAssignedDataVectors().get(0)
					.getPosition().length; i++) {
				centroid.getPosition()[i] = centroid.getPosition()[i]
						/ centroid.getAssignedDataVectors().size();
			}
		}

	}

	public double getC1() {
		return c1;
	}

	public void setC1(double c1) {
		this.c1 = c1;
	}

	public double getC2() {
		return c2;
	}

	public void setC2(double c2) {
		this.c2 = c2;
	}

	public double[] getEuclidianDistances() {
		return euclidianDistances;
	}

	public void setEuclidianDistances(double[] euclidianDistances) {
		this.euclidianDistances = euclidianDistances;
	}

	public String getFileName() {
		return fileName;
	}

	public void setFileName(String fileName) {
		this.fileName = fileName;
	}

	public Particle getGlobalBestPosition() {
		return globalBestPosition;
	}

	public void setGlobalBestPosition(Particle globalBestPosition) {
		this.globalBestPosition = globalBestPosition;
	}

	public List<Particle> getSwarm() {
		return swarm;
	}

	public void setSwarm(List<Particle> swarm) {
		this.swarm = swarm;
	}

	public List<DataVector> getTrainningSet() {
		return trainningSet;
	}

	public void setTrainningSet(List<DataVector> trainningSet) {
		this.trainningSet = trainningSet;
	}

	public double getwMax() {
		return wMax;
	}

	public double getwMin() {
		return wMin;
	}

	public void setwMax(double wMax) {
		this.wMax = wMax;
	}

	public void setwMin(double wMin) {
		this.wMin = wMin;
	}

}
